from decimal import Decimal

from django.shortcuts import render
import json
import time
import logging
logger = logging.getLogger('django')

from django_redis import get_redis_connection
from rest_framework.permissions import IsAuthenticated

from rest_framework.response import Response
from rest_framework.views import APIView
from list.models import Course
# Create your views here.



# 一：先创建购物车的子应用
# 二：编写添加购物车的视图
class CartsAddView(APIView):

    # 重写post方法:添加是post的方法

    # 先登录才能添加购物车-->一定要先验证
    # 有特殊的方法验证用户是否登陆

    # 但这里我们如果一定要验证用户登录，就会导致用户进不来这个函数，
    # 别忘了有过期token还有第二种错误异常是用户未登录(没有token)
    # 所以我们在这里把permission设置为空
    # permission_classes = [IsAuthenticated]
    permission_classes = []

    def post(self,request):
        try:
            # 第一步，获取参数 但必须是登陆的用户才能添加近购物车
            course_id = request.data.get('course_id')
            # 添加的数量-->但因为数量是只能添加一次，所以后面需要验证，传多的默认也是1条
            # 数量记得类型转换
            quantity = int(request.data.get('quantity',1))
            # 获取用户id 但这里有直接获取的方法，不用传递user_id
            user_id = request.user.id

            # 直接进行判断：直接用提供的方法判断用户是否登陆
            if not request.user.is_authenticated:
                return Response({
                    'code':2,   # 因为要与下面的异常区分，所以用2
                    'message':'用户未登陆'
                })


            if not user_id:
                return Response({
                    'code':0,
                    'message':'用户未登陆'
                })

            if not course_id:
                return Response({
                    'code':0,
                    'message':'课程id不能为空'
                })


            # 再查一下这个课程是否在表中真的存在:用到DoesNotExist异常
            try:
                course = Course.objects.get(id=course_id,is_deleted=False)

            except Course.DoesNotExist:
                return Response({
                    'code':0,
                    'message':'课程不存在'
                })


            # 再判断课程是否上架：
            if course.status != 1:
                return Response({
                    'code':0,
                    'message':'课程未上架'
                })

            # 再判断：课程是否是免费的，免费的不用加入购物车：
            if course.course_type == 1:
                return Response({
                    'code':0,
                    'message':'课程是免费的，不用加入购物车'
                })

            # 如果以上都符合就可以加入缓存redis中：
            # redis加入购物车的数据类型为：但cache中只能存字符串；因此要使用哈西的方法添加：hadd
            # hash哈希用来类型添加商品信息；
            # sort set(zadd)有序集合用来给商品进行排序
            # 但因为哈西类型是字典：大key 包 小key
            # 大key应该是用户的id 小key的键应该是课程id-->小value应该为课程的相关信息

            # 1.所以先将redis进行连接：
            redis_conn = get_redis_connection('default')  # 这里用的是默认的dev的redis配置
            # 2.使用redis的hset方法添加哈希数据类型
            # 他的参数都有name；key；value
            # name：为大key-->用户user_id(已经传过来了)
            # key：为小key-->课程course_id(已传 已校验)
            # value：为小value-->课程的相关信息
            # 所以我们在写最后value的数据时，就要思考加入购物车的到底有那些数据，并将value提出来写
            # 比如：课程id，课程名称，课程价格，课程是否选中，课程观看有效期，课程选购数量(就一个，传不传都行因为有默认值)

            course_message = {
                'name':course.name,
                'price':float(course.price),     # 因为本身的类型是Decimal类型，所以要转换成Float类型
                'is_selected':1,      # 默认选中
                'quantity':quantity,
                # 有效期是可以选择不同期限的，先不写
            }

            # 需要定义user_id的key，因为购物车数据是保存在redis中的，所以要定义user_id的key
            # 然后将course_id作为小key，course_message作为value
            carts_key = f'carts:view:shopping:{user_id}'
            course_key = f"course:{course_id}"


            # 最后在判断购物车中是否重复添加课程
            # carts_key是user_id
            if redis_conn.hexists(carts_key,course_key):
                return Response({
                    'code':0,
                    'message':'课程已存在'
                })


            # 开始写入数据
            # 但我们的数据是需要反序列化的，将字典格式转为字符串，json.dumps方法
            redis_conn.hset(carts_key,course_key,json.dumps(course_message))


            # 最后进行排素，一定是加入时间越近，最先展示
            # 所以用到有序集合：zadd
            # 先设置加入时间key
            # order_key = f'carts:view:order:{user_id}'
            # redis_conn.zadd(order_key,f'{course_id}.{time.time()}')
            logger.info('添加购物车成功')
            return Response({
                'code':1,
                'message':'添加购物车成功',
                'data':course_message   # 返回你添加的课程信息，因为你要给前端展示啊，一定记得返回
            })



        except Exception as e:
            logger.error(f'错误信息{e}')
            return Response({
                'code':0,
                'message':f'添加购物车失败,错误信息为{e}'
            })



# 查看购物车视图
class CartListView(APIView):

    # 查询购物车的信息是get方法：

    # 还是验证购物车是否登录
    # permission_classes = [IsAuthenticated]
    # 但我们还是做一个手动验证异常处理，把他置空
    permission_classes = []


    def get(self,request):
        try:
            # 接收参数
            # 要接收userid-->你要查哪个用户的购物车？
            user_id = request.user.id

            if not request.user.is_authenticated:
                logger.error('用户未登录')
                return Response({
                    'code':2,
                    'message':'用户未登录'
                })

            # 可以请求链接我们的redis查数据了
            redis_conn = get_redis_connection('default')
            # 因为同样我们写入的数据是哈希类型，和上面你定义的大key毅杨，拿下来进行查询
            # 需要定义user_id的key，因为购物车数据是保存在redis中的，所以要定义user_id的key
            # 然后将course_id作为key，course_message作为value
            carts_key = f'carts:view:shopping:{user_id}'  # 大key
            # course_key = f"course:{course_id}"
            # 开始查询数据：哈西的hgetall方法，获取所有添加进入购物车的课程id
            choose_course_id = redis_conn.hgetall(carts_key)   # 选择加入购物车的课程id们
            if not choose_course_id:
                return Response({
                    'code':0,
                    'message':'购物车为空'
                })

            # 因为不知道这个数据现在是什么类型格式，我们需要打印查看
            logger.info(choose_course_id)
            logger.info(type(choose_course_id))

            # [{b'course:2': b'{"name": "\\u91d1\\u79cb\\u5929\\u6211\\u4e5f\\u7231\\u4f60", "price
            # ": 950.0, "is_selected": 1, "quantity": 1}', b'course:1': b'{"name": "\\u5f20\\u5143\\u82f1\\u6211\\u7231\\u4f60", "price": 999.0,
            #  "is_selected": 1, "quantity": 1}'}]
            # 格式为这个b开头的，其实他就是字节格式
            # 处理需要解解码decord()方法：把字节转为字符串  |  encode()编码把字符串转为字节


            # 都验证没问题了之后，思路就是把转换好的数据放入新的字典，还是以键值对的字典形式
            # 定义一个空列表
            carts_list = []
            # 计算的购物车总价格，然后也要返回给前端
            total_price = Decimal('0.00')

            # 因为最外层是列表，我们可以遍历处理，里面全是一条条的字典，所以使用items的方法遍历键值对
            for course_id,course_value in choose_course_id.items():
                # 这里循环的是小key课程id，value是小value
                course_id_decode = course_id.decode()
                logger.info(course_id_decode)  # course:2
                logger.info(type(course_id_decode))   # [{"name": "\u91d1\u79cb\u5929\u6211\u4e5f\u7231\u4f60",}]

                course_value_decode = course_value.decode()
                logger.info(course_value_decode)
                logger.info(type(course_value_decode))
                # 但因为上面返回的课程id格式为：course:2
                # 所以需要将他分割再索引取值
                split_course_id = course_id_decode.split(':')
                logger.info(split_course_id[1])   # 1   太好了啊啊啊啊
                courseId = split_course_id[1]

                # 解包之后的数据就是字符串形式，是我们想要的，所以将信息的字典字符串格式 转化为字典格式加入新列表
                course_value_dict = json.loads(course_value_decode)

                # 因为购物车的数量和金额这种容易变化的东西最好是在数据库中进行查询
                course = Course.objects.get(id=courseId)   # for循环购物车中的课程id中查找到了这些具体的课程对象
                total_price += Decimal(str(course.price))   # 这里是计算总价格，因为数据库中的价格是Decimal类型，所以这里需要将数据库中的价格转化为字符串，然后转化为Decimal类型

                # 先定义一个字典格式，再把字典格式放入空列表
                my_course_info = {
                    'id':courseId,
                    'name':course.name,
                    'price':float(course.price),
                    'is_selected':course_value_dict['is_selected'],
                    'quantity':course_value_dict['quantity'],
                    # 别忘记还要返回图片
                    'course_img':course.course_img

                }
                # 我们定义的空列表就可以加入了
                carts_list.append(my_course_info)


            return Response({
                "code": 1,
                "message": "获取购物车列表成功",
                "data": {
                    'carts_list':carts_list,
                    'total_price':total_price
                }
            })


        except Exception as e:
            logger.error(f'错误信息{e}')
            return Response({
                'code':0,
                'message':f'查询购物车失败,错误信息为{e}'
            })




class CartsDeleteView(APIView):

    def post(self,request):
        try:
            # 同样还是先要登陆
            if not request.user.is_authenticated:
                logger.error('用户未登录')
                return Response({
                    'code':2,
                    'message':'用户未登录'
                })

            # 还是接收参数：要删除那些课程
            # 删除是分为两种情况：
            # 1.删除单个课程
            '''
            我们先定义一个课程参数：course_id = 1 这是整数类型
            批量删除的话：我们是一个列表类型[1,2,3,4]
            所以我们可以先判断是不是列表类型，如果是列表类型，就批量删除，如果不是，就单个删除
            单个删除的话，我们需要先查一下这个课程是否存在，如果存在，就删除，不存在就提示不存在
            '''

            # 获取参数
            course_ids = request.data.get('course_ids')
            if not course_ids:
                return Response({
                    'code':0,
                    'message':'请选择你要删除的课程'
                })

            # 判断类型：
            if isinstance(course_ids,int):
                # 如果不是int类型，那就也把他转为列表类型
                course_ids = [course_ids]  # 因为你下面无论如何删除的方式都是遍历删除，列表最适合不过了
            elif not isinstance(course_ids,list):
                # 因为如果不是列表类型不是数字类型，他也有可能是别的类型，你都要考虑到，所以这里要判断类型
                return Response({
                    'code':0,
                    'message':'课程id类型不正确'
                })

            # 如果都符合，开始删除
            # 构建redis连接
            redis_conn = get_redis_connection('default')
            # 定义user_id的key
            user_id = request.user.id
            carts_key = f'carts:view:shopping:{user_id}'

            # 想知道删了几节课
            delete_count = 0
            for course_id in course_ids:
                # 先判断这个课程是否存在
                # 这个key是你加入购物车的课程id，是否存在于你的购物车里
                course_key = f"course:{course_id}"
                if redis_conn.hexists(carts_key,course_key):
                    # 如果存在，就删除
                    redis_conn.hdel(carts_key,course_key)
                    delete_count += 1

            return Response({
                'code':1,
                'message':f'删除成功，共删除{delete_count}条课程',
                'data':{
                    'delete_count':delete_count
                }   # 删除几条你要返回给前端展示
            })



        except Exception as e:
            logger.error(f'错误信息{e}')
            return Response({
                'code':0,
                'message':f'删除购物车失败,错误信息为{e}'
            })



class CartsSelectedView(APIView):
    # 同样还是先验证登陆
    permission_classes = []

    def post(self,request):

        try:
            # 同样还是先获取参数
            # 但全选分析：传递的是选中状态，因为选择就是全部选中和不全部选中
            # 选重视1 true，不选是0 false
            if not request.user.is_authenticated:
                logger.error('用户未登录')
                return Response({
                    'code':2,
                    'message':'用户未登录'
                })

            # 接收参数：
            is_selected = request.data.get('is_selected')
            # 判断参数：
            if is_selected is None:   # 如果不写none，系统会把你传过来的0识别为布尔值false
                logger.error('请对课程进行操作')
                return Response({
                    'code':0,
                    'message':'请对课程进行操作'
                })

            # 判断参数的值是0、1
            if is_selected not in [0,1]:
                logger.error('参数格式不正确')
                return Response({
                    'code':0,
                    'message':'参数格式不正确'
                })


            # 如果以上都符合
            # 构建redis连接
            redis_conn = get_redis_connection('default')
            # 大key 定义user_id的key
            user_id = request.user.id
            carts_key = f'carts:view:shopping:{user_id}'
            # 获取所有的课程，进行循环处理
            big_data = redis_conn.hgetall(carts_key)   # 这玩意还是字节

            # 判断是否有值：
            if not big_data:
                logger.error('购物车为空')
                return Response({
                    'code':0,
                    'message':'购物车为空'
                })

            # 切换次数
            change_count = 0
            for course_key,course_value in big_data.items():
                # 这个value还是要进行解包-->转换字典类型进行取值(取状态)
                course_value_decode = json.loads(course_value.decode())
                # 更改状态数值：
                # 如果 你存在redis中的课程的状态和你传过来的要改变的状态不同(肯定不同哈)
                if course_value_decode['is_selected'] != is_selected:
                    # 那我就把你变成传过来的状态值
                    course_value_decode['is_selected'] = is_selected
                    change_count += 1
                    # 然后重新保存redis
                    redis_conn.hset(carts_key,course_key,json.dumps(course_value_decode))

            action = "全选" if is_selected == 1 else "取消全选"  # 这句没懂
            logger.info(f'{user_id}用户修改{action}购物车{big_data}全选状态成功,更新了{change_count}条')
            return Response({
                'code':1,
                'message':'购物车全选状态成功',
                'data':{
                    'change_count':change_count
                }
            })

        except Exception as e:
            logger.error(f'全选切换失败，错误信息{e}')
            return Response({
                'code':0,
                'message':f'全选切换失败，错误信息为{e}'
            })



class CartsNoView(APIView):

    permission_classes = []

    def post(self, request):
        try:

            if not request.user.is_authenticated:
                logger.error('用户未登录')
                return Response({
                    'code':2,
                    'message':'用户未登录'
                })

            # 这里接受参数应该是course_id，因为你课程id就知道了这个课程的当前状态
            # 直接取反就行啊，根本不用传参数好吧
            # 但这里穿的id也有可能是一个，也有肯恶搞十多个，我们都按照列表进行处理
            course_ids = request.data.get('course_ids')
            if not course_ids:
                logger.error('请选择要操作的课程')
                return Response({
                    'code':0,
                    'message':'请选择要操作的课程'
                })

            # 判断数据类型
            if isinstance(course_ids,int):
                course_ids = [course_ids]   # 转为列表


            if not isinstance(course_ids,list):
                logger.error('参数格式错误')
                return Response({
                    'code':0,
                    'message':'参数格式错误'
                })


            # 构建redis连接
            redis_conn = get_redis_connection('default')
            # 定义user_id的key
            user_id = request.user.id
            carts_key = f'carts:view:shopping:{user_id}'



            # 开始计数
            change_count = 0
            # 获取所有的课程数据，进行循环处理
            updated_course = []
            # 开始处理:循环验证传过来的参数id是否存在购物车数据中
            for course_id in course_ids:
                # 同样接收课程id，要进行验证的
                # 这个key是你加入购物车的课程id，是否存在于你的购物车里
                course_key = f"course:{course_id}"
                if redis_conn.hexists(carts_key,course_key):
                    # 存在
                    # 获取所有的课程数据，进行循环处理
                    course_value_decode = json.loads(redis_conn.hget(carts_key,course_key).decode())
                    # 改变状态
                    # 该行代码用于切换购物车中课程的选中状态（is_selected）。
                    # 若原状态为 0（未选中），则设为 1（选中）；否则设为 0。
                    # 实现方式：使用三元运算简化判断逻辑
                    course_value_decode['is_selected'] = 1 if course_value_decode['is_selected'] == 0 else 0
                    change_count += 1
                    updated_course.append({
                        'course_id': course_id,
                        "is_selected": course_value_decode['is_selected']
                    })
                    # 重新保存
                    redis_conn.hset(carts_key,course_key,json.dumps(course_value_decode))

            return Response({
                'code':1,
                'message':'切换成功',
                'data':{
                    'change_count':change_count,
                    'updated_course':updated_course
                }
            })

        except Exception as e:
            logger.error(f'切换反选失败，错误信息{e}')
            return Response({
                'code':0,
                'message':f'切换反选失败，错误信息为{e}'
            })


